import React, { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import {
  CancelDrop,
  closestCenter,
  pointerWithin,
  rectIntersection,
  CollisionDetection,
  DndContext,
  DragOverlay,
  DropAnimation,
  getFirstCollision,
  KeyboardSensor,
  MouseSensor,
  TouchSensor,
  Modifiers,
  UniqueIdentifier,
  useSensors,
  useSensor,
  MeasuringStrategy,
  KeyboardCoordinateGetter,
  defaultDropAnimationSideEffects,
  useDroppable,
  useDraggable,
} from '@dnd-kit/core';
import {
  SortableContext,
  useSortable,
  arrayMove,
  verticalListSortingStrategy,
  SortingStrategy,
} from '@dnd-kit/sortable';

import { coordinateGetter as multipleContainersCoordinateGetter } from './multipleContainersKeyboardCoordinates';

import { Item, Container, ContainerProps } from './components';

import { createRange } from './utilities';

function DroppableContainer({
  children,
  columns = 1,
  disabled,
  id,
  items,
  style,
  ...props
}: ContainerProps & {
  disabled?: boolean;
  id: UniqueIdentifier;
  items: UniqueIdentifier[];
  style?: React.CSSProperties;
}) {
  const {
    active,
    over,
    setNodeRef,
    isOver,
  } = useDroppable({
    id,
    data: {
      type: 'container',
    },
  });
  const isOverContainer = over ? (id === over.id && active?.data.current?.type !== 'container') || items.includes(over.id) : false;
  console.log('isOver', isOver);
  return (
    <Container
      ref={disabled ? undefined : setNodeRef}
      style={{
        ...style,
      }}
      hover={isOverContainer}
      columns={columns}
      {...props}
    >
      {children}
    </Container>
  );
}

const dropAnimation: DropAnimation = {
  sideEffects: defaultDropAnimationSideEffects({
    styles: {
      active: {
        opacity: '0.5',
      },
    },
  }),
};

type Items = Record<UniqueIdentifier, { id: UniqueIdentifier, value: string }[]>;

interface Props {
  adjustScale?: boolean;
  cancelDrop?: CancelDrop;
  columns?: number;
  containerStyle?: React.CSSProperties;
  coordinateGetter?: KeyboardCoordinateGetter;
  getItemStyles?(args: {
    value: UniqueIdentifier;
    index: number;
    overIndex: number;
    isDragging: boolean;
    containerId: UniqueIdentifier;
    isSorting: boolean;
    isDragOverlay: boolean;
  }): React.CSSProperties;
  wrapperStyle?(args: { index: number }): React.CSSProperties;
  itemCount?: number;
  items?: Items;
  handle?: boolean;
  renderItem?: any;
  strategy?: SortingStrategy;
  modifiers?: Modifiers;
  minimal?: boolean;
  trashable?: boolean;
  scrollable?: boolean;
}

export function MultipleContainers({
  adjustScale = false,
  itemCount = 3,
  cancelDrop,
  columns,
  handle = false,
  containerStyle,
  coordinateGetter = multipleContainersCoordinateGetter,
  getItemStyles = () => ({}),
  wrapperStyle = () => ({}),
  minimal = false,
  modifiers,
  renderItem,
  scrollable,
}: Props) {

  const [commands, setCommands] = useState<any[]>(() => {
    return createRange(itemCount, (index) => ({
      id: window.crypto.randomUUID(),
      value: `A${index + 1}`
    }))
  })

  const [items, setItems] = useState<any[]>([]);

  const containers: UniqueIdentifier[] = ['commands', 'items'];
  const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);

  const recentlyMovedToNewContainer = useRef(false);
  const isSortingContainer = activeId != null ? containers.includes(activeId) : false;

  /**
   * 针对多个容器优化的自定义碰撞检测策略
   * Custom collision detection strategy optimized for multiple containers 
   *
   * - First, find any droppable containers intersecting with the pointer.
   * - If there are none, find intersecting containers with the active draggable.
   * - If there are no intersecting containers, return the last matched intersection
   *
   */
  const collisionDetectionStrategy: CollisionDetection = useCallback(
    (args) => {

      // Start by finding any intersecting droppable
      const pointerIntersections = pointerWithin(args);
      const intersections =
        pointerIntersections.length > 0
          ? // If there are droppables intersecting with the pointer, return those
          pointerIntersections
          : rectIntersection(args);
      let overId = getFirstCollision(intersections, 'id') ?? null;

      if (overId !== null) {

        if (containers.includes(overId)) {
          const containerItems = items;

          // If a container is matched and it contains items (columns 'A', 'B', 'C')
          if (containerItems.length > 0) {
            // Return the closest droppable within that container
            overId = closestCenter({
              ...args,
              droppableContainers: args.droppableContainers.filter(
                (container) =>
                  container.id !== overId &&
                  containerItems.find((item) => item.id === container.id)
              ),
            })[0]?.id;
          }
        }

        return [{ id: overId }];
      }

      return [];


    },
    [activeId, items]
  );

  const [clonedCommands, setClonedCommands] = useState<any>();
  const [clonedItems, setClonedItems] = useState<any>();
  const sensors = useSensors(
    useSensor(MouseSensor),
    useSensor(TouchSensor),
    useSensor(KeyboardSensor, {
      coordinateGetter,
    })
  );
  const findContainer = (id: UniqueIdentifier) => {
    if (containers.includes(id)) {
      return id;
    }
    if (commands.find((item) => item.id === id)) {
      return 'commands';
    } else if (items.find((item) => item.id === id)) {
      return 'items';
    }
  };

  const getIndex = (id: UniqueIdentifier) => {
    const container = findContainer(id);

    if (!container) {
      return -1;
    }

    if (container === 'commands') {
      return commands.findIndex((item) => item.id === id);
    } else if (container === 'items') {
      return items.findIndex((item) => item.id === id);
    } else {
      return -1;
    }

  };

  const onDragCancel = () => {
    if (clonedItems) {
      // Reset items to their original state in case items have been
      // Dragged across containers
      setItems(clonedItems);
    }
    if (clonedCommands) {
      setCommands(clonedCommands);
    }

    setActiveId(null);
    setClonedItems(null);
    setClonedCommands(null);
  };

  useEffect(() => {
    requestAnimationFrame(() => {
      recentlyMovedToNewContainer.current = false;
    });
  }, [items]);

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={collisionDetectionStrategy}
      measuring={{
        droppable: {
          strategy: MeasuringStrategy.Always,
        },
      }}
      onDragStart={({ active }) => {
        setActiveId(active.id);
        setClonedItems(items);
        setClonedCommands(commands);
      }}
      onDragOver={({ active, over }) => {

        const overId = over?.id ?? null;

        if (overId === null || containers.includes(active.id)) {
          return
        }

        const overContainer = findContainer(overId);
        const activeContainer = findContainer(active.id);

        if (!overContainer || !activeContainer) {
          return
        }
        // 切换了容器，需要重新计算索引
        if (activeContainer === 'commands' && overContainer === 'items') {

          setItems((items) => {
            const activeItems = commands;
            const activeIndex = activeItems.findIndex((item) => item.id === active.id);

            const overItems = items;
            const overIndex = overItems.findIndex((item) => item.id === overId);

            let newIndex: number;

            if (containers.includes(overId)) {
              newIndex = overItems.length + 1;
            } else {
              const isBelowOverItem =
                over &&
                active.rect.current.translated &&
                active.rect.current.translated.top >
                over.rect.top + over.rect.height;

              const modifier = isBelowOverItem ? 1 : 0;

              newIndex =
                overIndex >= 0 ? overIndex + modifier : overItems.length + 1;
            }

            recentlyMovedToNewContainer.current = true;

            return [
              ...items.slice(0, newIndex),
              activeItems[activeIndex],
              ...items.slice(
                newIndex,
                items.length
              ),
            ]
          });
          setCommands((commands) => commands.map((item) => item.id !== active.id ? item : { ...item, id: window.crypto.randomUUID() }));
        }
      }}
      onDragEnd={({ active, over }) => {
        const overId = over?.id ?? null;
        if (overId === null) {
          setActiveId(null);
          return
        }
        const activeContainer = findContainer(active.id);
        const overContainer = findContainer(overId);

        if (activeContainer === 'items' && overContainer === 'items') {

          const activeIndex = items.findIndex((item) => item.id === active.id);
          const overIndex = items.findIndex((item) => item.id === overId);

          if (activeIndex !== overIndex) {
            setItems((items) => (arrayMove(
              items,
              activeIndex,
              overIndex
            )))
          }
        }

        setActiveId(null);
      }}
      cancelDrop={(args) => {
        // 如果拖拽元素在容器外，则取消
        return !args.over;
      }}
      onDragCancel={onDragCancel}
      modifiers={modifiers}
    >
      <div
        style={{
          display: 'inline-grid',
          boxSizing: 'border-box',
          padding: 20,
          gridAutoFlow: 'column',
        }}
      >
        <div className='commands-container' style={{
          display: 'flex',
          flexDirection: 'column',
          gap: 10,
          width: 200,
          border: '1px solid #dfdfdf',
          padding: 10,
        }}>
          {commands.map((value, index) => {
            return (
              <DraggableItem
                disabled={isSortingContainer}
                key={value.id}
                id={value.id}
                value={value.value}
                index={index}
                handle={handle}
                style={getItemStyles}
                wrapperStyle={wrapperStyle}
                renderItem={renderItem}
                containerId='commands'
                getIndex={getIndex}
              />
            );
          })}
        </div>

        <DroppableContainer
          id='items'
          label='列表视图'
          columns={columns}
          items={items.map((item) => item.id)}
          scrollable={scrollable}
          style={containerStyle}
          unstyled={minimal}
        >
          <SortableContext items={items.map((item) => item.id)} strategy={verticalListSortingStrategy}>
            {items.map((value, index) => {
              return (
                <SortableItem
                  disabled={isSortingContainer}
                  key={value.id}
                  id={value.id}
                  value={value.value}
                  index={index}
                  handle={handle}
                  style={getItemStyles}
                  wrapperStyle={wrapperStyle}
                  renderItem={renderItem}
                  containerId='items'
                  getIndex={getIndex}
                />
              );
            })}
          </SortableContext>
        </DroppableContainer>

      </div>
      {createPortal(
        <DragOverlay adjustScale={adjustScale} dropAnimation={dropAnimation}>
          {activeId
            ? renderSortableItemDragOverlay(activeId)
            : null}
        </DragOverlay>,
        document.body
      )}
    </DndContext>
  );

  function renderSortableItemDragOverlay(id: UniqueIdentifier) {
    const item = findContainer(id) === 'commands' ? commands.find((item) => item.id === id) : items.find((item) => item.id === id);
    return (
      <Item
        value={item?.value}
        handle={handle}
        style={getItemStyles({
          containerId: findContainer(id) as UniqueIdentifier,
          overIndex: -1,
          index: getIndex(id),
          value: id,
          isSorting: true,
          isDragging: true,
          isDragOverlay: true,
        })}
        color={getColor(id)}
        wrapperStyle={wrapperStyle({ index: 0 })}
        renderItem={renderItem}
        dragOverlay
      />
    );
  }
}

function getColor(id: UniqueIdentifier) {
  switch (String(id)[0]) {
    case 'A':
      return '#7193f1';
    case 'B':
      return '#ffda6c';
    case 'C':
      return '#00bcd4';
    case 'D':
      return '#ef769f';
  }

  return undefined;
}


interface SortableItemProps {
  containerId: UniqueIdentifier;
  id: UniqueIdentifier;
  value: string;
  index: number;
  handle: boolean;
  disabled?: boolean;
  style(args: any): React.CSSProperties;
  getIndex(id: UniqueIdentifier): number;
  renderItem(): React.ReactElement;
  wrapperStyle({ index }: { index: number }): React.CSSProperties;
}
// 可排序的
function SortableItem({
  disabled,
  id,
  value,
  index,
  handle,
  renderItem,
  style,
  containerId,
  getIndex,
  wrapperStyle,
}: SortableItemProps) {
  const {
    setNodeRef,
    setActivatorNodeRef,
    listeners,
    isDragging,
    isSorting,
    over,
    isOver,
    overIndex,
    transform,
    transition,
  } = useSortable({
    id,
    data: {
      type: containerId
    },
  });

  const mounted = useMountStatus();
  const mountedWhileDragging = isDragging && !mounted;

  return (
    <Item
      ref={disabled ? undefined : setNodeRef}
      value={value}
      dragging={isDragging}
      sorting={isSorting}
      handle={handle}
      handleProps={handle ? { ref: setActivatorNodeRef } : undefined}
      index={index}
      wrapperStyle={wrapperStyle({ index })}
      style={style({
        index,
        value,
        isDragging,
        isSorting,
        overIndex: over ? getIndex(over.id) : overIndex,
        containerId,
      })}
      color={getColor(id)}
      transition={transition}
      transform={transform}
      fadeIn={mountedWhileDragging}
      listeners={listeners}
      renderItem={renderItem}
    />
  );
}

// 可拖拽的
function DraggableItem({
  disabled,
  id,
  value,
  index,
  handle,
  renderItem,
  style,
  containerId,
  getIndex,
  wrapperStyle,
}: SortableItemProps) {
  const {
    setNodeRef,
    setActivatorNodeRef,
    listeners,
    isDragging,
    over,
    transform,
  } = useDraggable({
    id,
    data: {
      type: containerId
    },
  });

  return (
    <Item
      ref={disabled ? undefined : setNodeRef}
      value={value}
      dragging={isDragging}
      sorting={false}
      handle={handle}
      handleProps={handle ? { ref: setActivatorNodeRef } : undefined}
      index={index}
      wrapperStyle={wrapperStyle({ index })}
      style={style({
        index,
        value,
        isDragging,
        isSorting: false,
        overIndex: -1,
        containerId,
      })}
      color={getColor(id)}
      listeners={listeners}
      renderItem={renderItem}
    />
  );
}

function useMountStatus() {
  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    const timeout = setTimeout(() => setIsMounted(true), 500);

    return () => clearTimeout(timeout);
  }, []);

  return isMounted;
}
